import os
import subprocess
import sys
import time
import traceback

import pyautogui
import pygame
import PygameCrew as pycr
import pyperclip
from calca import almost, symbol

import asset.debugger_views as debugger_views
import asset.startup_check
import img.img_loader as imgs
from asset.base import *
from asset.constants import *
from control import *

assert sys.platform == "win32", "Can only run on Windows platform"


pycr.cgGRAV(0,0.5)
pycr.cgAIRFRICTION(0.05)
pycr.pcv.icon=imgs.ico_main
pycr.pcv.caption="Elivo Main Game Launcher    version " + version_string
pycr.PyCr_Value.length,pycr.PyCr_Value.width=1536, 864
pycr.screen=pycr.pcv.set_window()
Triggers:"set[str]"
IstTriggers:"set[str]"
Added_Ist:"set[str]"
loaded:"list[Game_Blocks]"
def main():
    global Player,Triggers,loaded,starttime,IstTriggers,Added_Ist,eff_set,name
    loop=True
    START=(0,824)
    def clear():
        global Player,Triggers,loaded,starttime,IstTriggers,Added_Ist,eff_set
        Player=player(START,pycr.overlap(START,(20,40)),((pygame.K_SPACE,pygame.K_UP,pygame.K_w),(pygame.K_LEFT,pygame.K_a),(pygame.K_RIGHT,pygame.K_d)))
        starttime=time.time()
        Triggers=set((True,))
        Added_Ist=set()
        IstTriggers=set()
        loaded=list()
        eff_set=effectable_list()
    def remove_trigger(tr:trigger):
        global loaded
        loaded.remove(tr)
    def trigger_str(string:str):
        """Trigger string handler"""
        global Triggers,IstTriggers
        Settings,Trig=string.split(SEPARATOR)
        ist=INSTANT in Settings
        enable=ANTI not in Settings
        rapid=RAPID in Settings
        if ist:
            if enable:
                IstTriggers.add(Trig)
                Added_Ist.add(Trig)
            else:
                try:IstTriggers.remove(Trig)
                except:...
                try:Added_Ist.remove(Trig)
                except:...
        else:
            if enable:Triggers.add(Trig)
            else:
                try:Triggers.remove(Trig)
                except:...
        return rapid
    def istriggered(string:str):
        global Triggers,IstTriggers
        return string in Triggers or string in IstTriggers
    def istype(a:object):return isinstance(a,type)
    def isinstance_or_is(a:object,b:"type|object")->bool:
        if istype(b) or (isinstance(b,Iterable) and all(map(istype,b))):return isinstance(a,b)
        else:return a is b
    def gene_fill(args:tuple,filltype:type,fillnumber:int=4)->"tuple":
        return (args+tuple(map(filltype,(fillnumber-len(args))*(tuple(),))))if len(args) <= fillnumber else args[:fillnumber]
    def findstopping(Stopouts:"list[tuple[Game_Blocks]]",passive_pattern:type=Movable,stopper_pattern:type=Pusher,*args):
        if len(args) == 0:
            args=tuple()
            args=gene_fill(args,set,4)
            for obj in eff_set:
                left,right,up,down=obj.rect.keepin()
                if left:args[0].add(obj)
                if right:args[1].add(obj)
                if up:args[2].add(obj)
                if down:args[3].add(obj)
        Stopped_push_block_left=args[0]
        Stopped_push_block_right=args[1]
        Stopped_push_block_up=args[2]
        Stopped_push_block_down=args[3]
        _all_stop=(Stopped_push_block_left,Stopped_push_block_right,Stopped_push_block_up,Stopped_push_block_down)
        Touching_Dict:"dict[platform,list[Game_Blocks]]"=dict()
        Possible_Starter:"list[platform]"=list()
        PlayerPush=None
        for passive, stopper in Stopouts:
            if isinstance(stopper,Pusher) and stopper is not passive:
                if Touching_Dict.get(stopper) is None:
                    Touching_Dict[stopper]=list()
                Touching_Dict[stopper].append(passive)
        for stopper in Touching_Dict.keys():
            Add=True
            for p_list in Touching_Dict.values():
                if stopper in p_list:Add=False;break
            if Add:
                if isinstance(stopper,player):
                    PlayerPush=stopper
                else:
                    Possible_Starter.append(stopper)
        if PlayerPush is not None:Possible_Starter.append(PlayerPush)
        def sort_recursively(This_Possible_Starter:Pusher):
            nonlocal Touching_Dict, Possible_Starter
            if Touching_Dict.get(This_Possible_Starter) is None or len(Touching_Dict.get(This_Possible_Starter))==0:...
            else:
                for obj in Touching_Dict[This_Possible_Starter]:
                    if Touching_Dict.get(obj) is not None and This_Possible_Starter in Touching_Dict.get(obj):
                        Touching_Dict.get(obj).remove(This_Possible_Starter)
                for obj in Touching_Dict[This_Possible_Starter]:
                    sort_recursively(obj)  
        for ps in Possible_Starter:
            sort_recursively(ps)
        def push_recursively(This_Possible_Starter:Pusher):
            nonlocal Touching_Dict, Possible_Starter
            if Touching_Dict.get(This_Possible_Starter) is None or len(Touching_Dict.get(This_Possible_Starter))==0:...
            else:
                for obj in Touching_Dict[This_Possible_Starter]:
                    obj.rect.stopout(This_Possible_Starter.rect)
                for obj in Touching_Dict[This_Possible_Starter]:
                    push_recursively(obj)
        for ps in Possible_Starter:
            push_recursively(ps)
        return (None,)*4
        print(_all_stop)
    def detect_effect(P:"effectable_list",load:Game_Blocks,save_Stopouts:list):
        global Triggers,loaded,starttime,IstTriggers,Added_Ist,eff_set
        nonlocal game_over,beated,winner
        # Subclass check
        if not isinstance(load,Game_Blocks):return
        for eff in P:
            eff:"player|player_interactive"
            isPlayer=isinstance(eff,player)
            isEff=isinstance(eff,player_interactive)
            touched=eff.rect.touched(load.rect) and eff is not load
            edgetouched=eff.rect.edgetouched(load.rect)
            touched_edge=touched or edgetouched
            if isinstance(load,push_block):
                if isPlayer and touched_edge:save_Stopouts.append((load,eff))
            if isinstance(load,platform):
                if touched_edge:
                    save_Stopouts.append((eff,load))
            if isinstance(load,speed_active):
                if edgetouched:
                    if isinstance(load,not_runnable):eff.rect.speed = combine(eff.rect.speed,load.runspeed,100 if isinstance(load,platform) else 1)
                    else:eff.rect.speed = combine(eff.rect.speed,load.rect.speed,100 if isinstance(load,platform) else 1)
            # Specific type check
            if isinstance(load,killer):
                if isPlayer and touched:game_over=True
            elif isinstance(load,warp):
                if isPlayer and touched:
                    beated=name
                    winner=True
            elif isinstance(load,trigger):
                if isPlayer and touched:
                    if not trigger_str(load.trig):remove_trigger(load)
            elif isinstance(load,hook):
                if touched:eff.rect.speed=(eff.rect.speed[0],0);eff.rect.stopy()
            elif isinstance(load,water):
                if touched and (not isPlayer or eff.jumpbonus == 0):
                    if isPlayer:eff.clear_jump_data()
                    eff.rect.clearveracc()
                    eff.rect.speed=(eff.rect.speed[0],pycr.get_gravity()[1]*WATER_COMMON)
    def loading_handler(P:player):
        global Triggers,loaded,starttime,IstTriggers,Added_Ist,game_over,beated,winner,eff_set,name
        Stopouts:"list[tuple[player|player_interactive,Game_Blocks]]"=list()
        for load in loaded:
            touched=P.rect. touched(load.rect)
            tappear=True
            visible=True
            if isinstance(load,Game_Blocks):
                tappear=load.feature is None or load.feature.get("Appear") in (None, True)
                visible=load.feature is None or load.feature.get("Visible") in (None, True)
                if load.feature is not None:
                    for f,option in load.feature.items():
                        try:
                            spec, trig=option
                            if istriggered(trig):
                                if isinstance(spec, dire) and tappear:
                                    mrlim, mrstr = spec.outend(load.rect),spec.outstart(load.rect)
                                    load.rect.speed = (spec.x, spec.y)
                                    if f == "single" and mrlim:
                                        load.rect.speed=(0,0)
                                    elif f == "repeat" and mrlim:
                                        load.rect.absolute(*spec.start)
                                    elif f == "turn":
                                        spec.turn(load.rect,mrlim,mrstr)
                                elif spec is Appear:
                                    load.feature["Appear"]=True
                                elif f == "chase":
                                    if len(spec)<2:spec.append(None)
                                    spec[-1]=(dire(load.rect.start,P.rect.start,spec[0]))
                                    load.rect.speed=(spec[-1].x,spec[-1].y)
                                elif f == "aiming":
                                    if len(spec)<2:
                                        spec.append(dire(load.rect.start,P.rect.start,spec[0]))
                                    load.rect.speed=(spec[-1].x,spec[-1].y)
                        except:...
                if tappear:
                    # Subclass check
                    if isinstance(load,timer):
                        if istriggered(load.starttrig) and not P.unmoved:
                            if load.timer is None:load.start_timer()
                            ts=time.time()-load.timer
                            if ts >= load.etime:trigger_str(load.trig)
                    detect_effect(eff_set,load,Stopouts)
            findstopping(Stopouts)
            if tappear and visible:pycr.put(load)
            elif visible:load.rect.run()
        Stopouts_Stage2=list()
        
        
            
    
    clear()
    fps=50
    name=""
    game_over=False
    winner=False
    beated=None
    curBackground=BACKGROUND
    nameimg=None
    Editor_Button=pycr.button((0,0),(100,30),("Elivo Editor","Open"))
    About_Button=pycr.button((100,0),(200,30),("About","About"))
    gameover_restart=pycr.button((0,0),(1536,864),("...","Press R to Restart the Level"),100)
    while loop:
        #Front Part
        pycr.screen.fill(curBackground)
        pycr.get_interact()
        #Front-Part
        Player.run()
        if len(loaded) == 0:
            try:
                loaded,name=execloca()
                nameimg=pycr.text_part((200,0),"  Level Name: "+name+"    ctrl + w to skip    r to retry",color=REVBACK)
                eff_set.addif(Player)
                for load in loaded:eff_set.addif(load)
            except Exception as e:
                if igvar["location"] != "_default":
                    igvar["location"]="_default"
                else:
                    raise e
        for event in pycr.get_event():
            if event.type == pygame.QUIT:loop=False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    loop=False
                elif event.key == pygame.K_f:
                    if pycr.pcv.ctrl and pycr.pcv.alt:
                        fps=debugger_views.bebugview_1()
                elif event.key == pygame.K_v:
                    if pycr.pcv.ctrl and pycr.pcv.alt:
                        debugger_views.bebugview_2(fps=fps)
                elif event.key == pygame.K_w:
                    if pycr.pcv.ctrl:
                        winner=True
                elif event.key == pygame.K_r:game_over=True
        loading_handler(Player)
        if game_over:
            if pycr.PyCr_Value.key == pygame.K_r or pycr.put(gameover_restart):
                game_over=False
                clear()
        elif winner:
            text="Restart a level..." if beated is None else "You beated level '"+beated+"'!!!!!"
            igvar["location"]=pyautogui.prompt("Type another level name to play.\nNon-exist names will warp you back to '_default'.",text)
            winner=False  
            clear()
            beated=None
        else:  
            if nameimg is not None:
                pycr.put(nameimg)    
            
            if Player.unmoved:starttime=time.time();timestring="    Ready to Go"
            else:timestring="    Time Used: "+str(rbystr(time.time()-starttime,2))
            
        if winner:pycr.screen.fill(BLACK)
        timeimg=FONT.render(timestring,True,ORANGE)
        try:
            pycr.screen.blit(timeimg,nameimg.rect.ru)
        except:...
        pycr.put(Player)
        to_remove=list()
        for ist in IstTriggers:
            if ist not in Added_Ist:to_remove.append(ist)
            try:Added_Ist.remove(ist)
            except:...
        for tr in to_remove:IstTriggers.remove(tr)
        if pycr.put(Editor_Button):
            if os.path.exists("./ElivoEditor.exe"):
                subprocess.Popen("ElivoEditor.exe")
            elif os.path.exists("./ElivoEditor.pyw"):
                subprocess.Popen("pythonw ElivoEditor.pyw")
            elif os.path.exists("./ElivoEditor.pyc"):
                subprocess.Popen("pythonw ElivoEditor.pyc")
        elif pycr.put(About_Button):
            pyautogui.alert(ABOUT_MESSAGE,"Elivo main game & editor Info")
        #Ender-Part
        pycr.clock.tick(fps)
        pygame.display.flip()
        #Ender Part
if __name__ == "__main__":
    try:
        if len(sys.argv) > 1:
            igvar["location"]=mltsplit(sys.argv[1],'/','\\')[-1]
        main()
        pycr.QuitDisplay()
    except KeyboardInterrupt:...
    except:
        pycr.QuitDisplay()
        tr=traceback.format_exc()
        if pyautogui.alert(tr+"\n\nPlease send this message to liyz20@163.com.\nClick 'OK' to copy.","An Error Occurred") == "OK":
            pyperclip.copy(tr)
            pyautogui.alert(title="Message copied successfully.")
    save()
